#pragma

#include<iostream>
#include<vector>
#include <semaphore.h>
#include<cassert>
#include<pthread.h>
using namespace std;

const int gcap = 5;

template<class T>
class RingQueue
{
public:
    RingQueue(const int& cap = gcap)
    :_queue(cap) //调用vector的构造
    ,_cap(cap)
    {
        // 对信号量要初始化
        int n = sem_init(&_spaceSem, 0, _cap); //对于生产者一开始资源数量就是容量大小
        assert(n == 0);
        n = sem_init(&_dataSem, 0, 0); //对于消费者一开始没有资源
        assert(n == 0);

        _productorPos = _ConsumerPos = 0; // 一开始都在0下标处

        // 对锁也要初始化
        pthread_mutex_init(&_pmutex, nullptr);
        pthread_mutex_init(&_cmutex, nullptr);
    }

    ~RingQueue()
    {
        // 对锁和信号量要销毁
        int n = sem_destroy(&_spaceSem);
        assert(n == 0);
        n = sem_destroy(&_dataSem);
        assert(n == 0);

        pthread_mutex_destroy(&_pmutex);
        pthread_mutex_destroy(&_cmutex);
    }

    void push(const T& data)
    {
        // 生产——插入之前要先申请信号量，看看有没有资源给自己生产
        P(_spaceSem);// 要注意，先申请信号量在申请锁、信号量不需要被保护，它自己的操作就是原子操作
        pthread_mutex_lock(&_pmutex);  //先预定资源，再互斥访问，
        // 走到这里就是申请到了，不然会阻塞等待
        _queue[_productorPos++] = data;
        _productorPos %= _cap; // 符合环装队列的下标
        pthread_mutex_unlock(&_pmutex);
        V(_dataSem); // 释放信号量(消费者的)
    }

    void pop(T* out)
    {
        // 消费之前也要先申请信号量
        P(_dataSem); //先预定资源，再互斥访问，
        pthread_mutex_lock(&_cmutex);
        *out = _queue[_ConsumerPos++]; // 消费
        _ConsumerPos %= _cap;// 符合环装队列的下标
        pthread_mutex_unlock(&_cmutex);
        V(_spaceSem); //释放生产者的信号量
    }
private:
    // 这里封装一下PV操作，实际上不封装也是可以的
    void P(sem_t& sem) // P操作，预定信号量
    {
        int n = sem_wait(&sem); // P
        assert(n == 0);
        (void)n;
    }

    void V(sem_t& sem) // V操作，释放信号量
    {
        int n = sem_post(&sem); // V
        assert(n == 0);
        (void)n;
    }
private:
    vector<T> _queue; //以数组为底层
    int _cap; //环形队列的容量
    sem_t _spaceSem; //空间资源对生产者来说才是资源
    sem_t _dataSem; //数据资源对消费者来说才是资源

    // 下面这两个属性是可以不用的，这样会更清楚一点
    int _productorPos; // 生产者在队列中的下标
    int _ConsumerPos; // 消费者在队列中的下标

    // 在多消费，多生产的情况下，需要两把锁，来保证临界资源的安全
    pthread_mutex_t _pmutex;
    pthread_mutex_t _cmutex;
};
